//===--- Conversion.h - Types for value conversion --------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines the Conversion class as well as ConvertingInitialization.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_PIL_GEN_CONVERSION_H
#define POLARPHP_PIL_GEN_CONVERSION_H

#include "polarphp/basic/ExternalUnion.h"
#include "polarphp/pil/gen/Initialization.h"
#include "polarphp/pil/gen/SGFContext.h"

namespace polar::lowering {

/// An abstraction representing certain kinds of conversion that PILGen can
/// do automatically in various situations.
class Conversion {
public:
   enum KindTy {
      /// A bridging conversion to a foreign type.
         BridgeToObjC,

      /// A bridging conversion to a foreign type following a force.
      /// Although it's not reflected in the name, this is always an
      /// implicit force cast.
         ForceAndBridgeToObjC,

      /// A bridging conversion from a foreign type.
         BridgeFromObjC,

      /// A bridging conversion for a function result.
         BridgeResultFromObjC,

      /// An erasure to Any (possibly wrapped in optional conversions).
      /// This is sortof a bridging conversion.
         AnyErasure,

      LastBridgingKind = AnyErasure,

      /// An orig-to-subst conversion.
         OrigToSubst,

      /// A subst-to-orig conversion.  These can always be annihilated.
         SubstToOrig,
   };

   static bool isBridgingKind(KindTy kind) {
      return kind <= LastBridgingKind;
   }

private:
   KindTy Kind;

   struct BridgingTypes {
      CanType OrigType;
      CanType ResultType;
      PILType LoweredResultType;
      bool IsExplicit;
   };

   struct ReabstractionTypes {
      AbstractionPattern OrigType;
      CanType SubstType;
   };

   using Members = ExternalUnionMembers<BridgingTypes, ReabstractionTypes>;

   static Members::Index getStorageIndexForKind(KindTy kind) {
      switch (kind) {
         case BridgeToObjC:
         case ForceAndBridgeToObjC:
         case BridgeFromObjC:
         case BridgeResultFromObjC:
         case AnyErasure:
            return Members::indexOf<BridgingTypes>();

         case OrigToSubst:
         case SubstToOrig:
            return Members::indexOf<ReabstractionTypes>();
      }
      llvm_unreachable("bad kind");
   }

   ExternalUnion<KindTy, Members, getStorageIndexForKind> Types;
   static_assert(decltype(Types)::union_is_trivially_copyable,
                 "define the special members if this changes");

   Conversion(KindTy kind, CanType origType, CanType resultType,
              PILType loweredResultTy, bool isExplicit)
      : Kind(kind) {
      Types.emplaceAggregate<BridgingTypes>(kind, origType, resultType,
                                            loweredResultTy, isExplicit);
   }

   Conversion(KindTy kind, AbstractionPattern origType, CanType substType)
      : Kind(kind) {
      Types.emplaceAggregate<ReabstractionTypes>(kind, origType, substType);
   }

public:
   static Conversion getOrigToSubst(AbstractionPattern origType,
                                    CanType substType) {
      return Conversion(OrigToSubst, origType, substType);
   }

   static Conversion getSubstToOrig(AbstractionPattern origType,
                                    CanType substType) {
      return Conversion(SubstToOrig, origType, substType);
   }

   static Conversion getBridging(KindTy kind, CanType origType,
                                 CanType resultType, PILType loweredResultTy,
                                 bool isExplicit = false) {
      assert(isBridgingKind(kind));
      return Conversion(kind, origType, resultType, loweredResultTy, isExplicit);
   }

   KindTy getKind() const {
      return Kind;
   }

   bool isBridging() const {
      return isBridgingKind(getKind());
   }

   AbstractionPattern getReabstractionOrigType() const {
      return Types.get<ReabstractionTypes>(Kind).OrigType;
   }

   CanType getReabstractionSubstType() const {
      return Types.get<ReabstractionTypes>(Kind).SubstType;
   }

   bool isBridgingExplicit() const {
      return Types.get<BridgingTypes>(Kind).IsExplicit;
   }

   CanType getBridgingSourceType() const {
      return Types.get<BridgingTypes>(Kind).OrigType;
   }

   CanType getBridgingResultType() const {
      return Types.get<BridgingTypes>(Kind).ResultType;
   }

   PILType getBridgingLoweredResultType() const {
      return Types.get<BridgingTypes>(Kind).LoweredResultType;
   }

   ManagedValue emit(PILGenFunction &SGF, PILLocation loc,
                     ManagedValue source, SGFContext ctxt) const;

   /// Try to form a conversion that does an optional injection
   /// or optional-to-optional conversion followed by this conversion.
   Optional<Conversion>
   adjustForInitialOptionalConversions(CanType newSourceType) const;

   /// Try to form a conversion that does a force-value followed by
   /// this conversion.
   Optional<Conversion> adjustForInitialForceValue() const;

   void dump() const LLVM_ATTRIBUTE_USED;
   void print(llvm::raw_ostream &out) const;
};

/// Information about how to peephole two conversions.
///
/// This is really the private state of PILGenConvert.
class ConversionPeepholeHint {
public:
   enum Kind : uint8_t {
      /// The value will be exactly the right type.
         Identity,

      /// The value needs to be bridged to AnyObject (possibly optionally).
         BridgeToAnyObject,

      /// The value just needs to undergo a subtype conversion.
         Subtype
   };

private:
   Kind TheKind;
   bool Forced;

public:
   ConversionPeepholeHint(Kind kind, bool forced)
      : TheKind(kind), Forced(forced) {
   }

   Kind getKind() const { return TheKind; }

   /// Does the value need to be forced before the conversion?
   /// This comes up with result conversions where the result was imported
   /// as non-optional, as well as with implicitly unwrapped optionals.
   bool isForced() const { return Forced; }
};

Optional<ConversionPeepholeHint>
canPeepholeConversions(PILGenFunction &SGF,
                       const Conversion &outerConversion,
                       const Conversion &innerConversion);

ManagedValue emitPeepholedConversions(PILGenFunction &SGF, PILLocation loc,
                                      const Conversion &outerConversion,
                                      const Conversion &innerConversion,
                                      ConversionPeepholeHint hint,
                                      SGFContext C,
                                      ValueProducerRef produceValue);

/// An initialization where we ultimately want to apply a conversion to
/// the value before completing the initialization.
///
/// Value generators may call getAsConversion() to check whether an
/// Initialization is one of these.  If so, they may call either
/// tryPeephole or setConvertedValue.
class ConvertingInitialization final : public Initialization {
private:
   enum StateTy {
      /// Nothing has happened.
         Uninitialized,

      /// The converted value has been set.
         Initialized,

      /// finishInitialization has been called.
         Finished,

      /// The converted value has been extracted.
         Extracted
   };

   StateTy State;

   /// The conversion that needs to be applied to the formal value.
   Conversion TheConversion;

   /// The converted value, set if the initializing code calls tryPeephole,
   /// setReabstractedValue, or copyOrInitValueInto.
   ManagedValue Value;
   SGFContext FinalContext;

   StateTy getState() const {
      return State;
   }

public:
   ConvertingInitialization(Conversion conversion, SGFContext finalContext)
      : State(Uninitialized), TheConversion(conversion),
        FinalContext(finalContext) {}

   /// Return the conversion to apply to the unconverted value.
   const Conversion &getConversion() const {
      return TheConversion;
   }

   /// Return the context into which to emit the converted value.
   SGFContext getFinalContext() const {
      return FinalContext;
   }

   // The three ways to perform this initialization:

   /// Set the unconverted value for this initialization.
   void copyOrInitValueInto(PILGenFunction &SGF, PILLocation loc,
                            ManagedValue value, bool isInit) override;

   /// Given that the result of the given expression needs to sequentially
   /// undergo the the given conversion and then this conversion, attempt to
   /// peephole the result.  If successful, the value will be set in this
   /// initialization.  The initialization will not yet be finished.
   bool tryPeephole(PILGenFunction &SGF, Expr *E, Conversion innerConversion);
   bool tryPeephole(PILGenFunction &SGF, PILLocation loc,
                    Conversion innerConversion, ValueProducerRef producer);
   bool tryPeephole(PILGenFunction &SGF, PILLocation loc, ManagedValue value,
                    Conversion innerConversion);

   /// Set the converted value for this initialization.
   void setConvertedValue(ManagedValue value) {
      assert(getState() == Uninitialized);
      assert(!value.isInContext() || FinalContext.getEmitInto());
      Value = value;
      State = Initialized;
   }

   /// Given that an emitter was able to adjust the conversion when
   /// emitting into this initialization, continue emission into the
   /// new conversion.
   ManagedValue emitWithAdjustedConversion(PILGenFunction &SGF, PILLocation loc,
                                           Conversion adjustedConversion,
                                           ValueProducerRef producer);

   /// Given the unconverted result, i.e. the result of emitting a
   /// value formally of the unconverted type with this initialization
   /// as the SGFContext, produce the converted result.
   ///
   /// If this initialization was initialized, the unconverted result
   /// must be ManagedValue::forInContext(), and vice-versa.
   ///
   /// The result of this function may itself be
   /// ManagedValue::forInContext() if this Initialization was created
   /// with an SGFContext which contains another Initialization.
   ManagedValue finishEmission(PILGenFunction &SGF, PILLocation loc,
                               ManagedValue formalResult);

   // Implement to make the cast work.
   ConvertingInitialization *getAsConversion() override {
      return this;
   }

   // Bookkeeping.
   void finishInitialization(PILGenFunction &SGF) override {
      assert(getState() == Initialized);
      State = Finished;
   }
};

} // end namespace polar::lowering

#endif // POLARPHP_PIL_GEN_CONVERSION_H
